﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using System.Data.Services.Client;

namespace AntsCode.StorageClientExtensions.Helpers
{
    internal class SuffixQuery
    {
        public static DataServiceQuery<T> SuffixWhere<T>(Expression baseExpression, DataServiceQuery<T> data)
        {
            MethodCallExpression whereCall = ExpressionUtil.FindMethodCall(baseExpression, new string[] { "Where" });

            if (whereCall == null)
            {
                // No method to suffix
                return data;
            }

            UnaryExpression ue = (UnaryExpression)whereCall.Arguments[1];
            Expression sourceExp = (Expression)ue.Operand;
            Type sourceType = sourceExp.Type.GetGenericArguments()[0];
            Expression<Func<T, bool>> whereExp;

            // Is the type of the Where expression the same as the data?
            if (sourceType == typeof(T))
            {
                // Yes, so use the Where expression as-is
                whereExp = (Expression<Func<T, bool>>)ue.Operand;
            }
            else
            {
                // The type is different. This can occur if the parent query contains joins, as the Where type becomes a combination
                // of all joined types.
                // Here we try and convert the where expression to the data type
                IExpressionConverter converter = (IExpressionConverter)Activator.CreateInstance(typeof(ExpressionConverter<,>).MakeGenericType(sourceType, typeof(T)));
                whereExp = (Expression<Func<T, bool>>)converter.ConvertExpression(ue.Operand);
            }

            if (whereExp != null)
            {
                return (DataServiceQuery<T>)data.Where(whereExp);
            }
            else
            {
                return data;
            }
        }

        public static DataServiceQuery<T> SuffixExtras<T>(Expression baseExpression, DataServiceQuery<T> data)
        {
            // Look for other methods to suffix (there can only be one of these)
            MethodCallExpression methodCall = ExpressionUtil.FindMethodCall(baseExpression, new string[] { "First", "FirstOrDefault", "Take" });
            
            if (methodCall == null)
            {
                // No method to suffix
                return data;
            }

            if (IsSingleResource(data))
            {
                // A single resource will throw an exception if no resource exists, and also prevents
                // the Take() methods below from being applied, so here we force the query not to return a single resource
                data = (DataServiceQuery<T>)data.Where(x => true);
            }

            switch (methodCall.Method.Name)
            {
                case "First":
                case "FirstOrDefault":
                    // First & FirstOrDefault perform a top(1) in the underlying query
                    return (DataServiceQuery<T>)data.Take(1);

                default: // Take
                    ConstantExpression ce = (ConstantExpression)methodCall.Arguments[1];
                    int count = (int)ce.Value;
                    return (DataServiceQuery<T>)data.Take(count);
            }
        }

        public static bool IsSingleResource<T>(DataServiceQuery<T> query)
        {
            bool isSingleResource = false;
            Expression exp = query.Expression;

            // Does this query have a where expression?
            if (exp != null)
            {
                if (exp.NodeType == ExpressionType.Call)
                {
                    // Yes, get the binary expression
                    MethodCallExpression methodExp = (MethodCallExpression)exp;
                    LambdaExpression lambdaExp = (LambdaExpression)((UnaryExpression)methodExp.Arguments[1]).Operand;

                    if (lambdaExp.Body.NodeType == ExpressionType.AndAlso)
                    {
                        BinaryExpression binExp = (BinaryExpression)lambdaExp.Body;
                        isSingleResource = IsSingleResource<T>(binExp);
                    }
                }
            }

            return isSingleResource;
        }

        public static bool IsSingleResource<T>(BinaryExpression binExp)
        {
            // To determine if this is a single resource, there where expression must be
            // a binary expression, with PartitionKey on the left & RowKey on the right
            bool isSingleResource = false;

            if (binExp.Left.NodeType == ExpressionType.Equal && binExp.Right.NodeType == ExpressionType.Equal)
            {
                BinaryExpression leftExp = (BinaryExpression)binExp.Left;
                BinaryExpression rightExp = (BinaryExpression)binExp.Right;

                if (IsMemberOfName(leftExp, "PartitionKey") && IsMemberOfName(rightExp, "RowKey"))
                {
                    isSingleResource = true;
                }
            }

            return isSingleResource;
        }

        private static bool IsMemberOfName(BinaryExpression exp, string name)
        {
            bool isMemberOfName = false;
            Expression binaryPart;

            for (int i = 0; i <= 1; i++)
            {
                if (i == 0)
                    binaryPart = exp.Left;
                else
                    binaryPart = exp.Right;

                if (binaryPart.NodeType == ExpressionType.MemberAccess)
                {
                    MemberExpression memExp = (MemberExpression)binaryPart;

                    if (memExp.Member.Name == name)
                    {
                        isMemberOfName = true;
                    }
                }
            }

            return isMemberOfName;
        }
    }
}